import './index.scss';
import {iReactivityApi, RenderNode} from "../../packages";
import {createClasses} from "../../packages/utils/createClass";
import {createRef} from "../../packages/utils/createRef";
import {formatNumber, hasConflicts, iCmsRangeItemData} from "./cms.image.range.utils";

/**
 * 热区容器组件以及热区节点组件
 * @author  韦胜健
 * @date    2022/3/6 15:25
 */
export function createCmsRange({ type, designComponent, provide, inject, reactive }: iReactivityApi) {
  const classes = createClasses(type);

  const CmsRange = designComponent({
    props: {
      list: {},
    },
    emits: {
      onAdd: (val: iCmsRangeItemData) => true,
    },
    slots: ['default'],
    setup({ props, event: { emit }, slots }: {
      props: { list?: iCmsRangeItemData[] },
      event: { emit: { onAdd: (val: iCmsRangeItemData) => void } },
      slots: { default: () => RenderNode },
    }) {

      const refEl = createRef();

      let state = reactive({
        drag: {
          dragging: false,
          start: { x: 0, y: 0, },
          move: { x: 0, y: 0 },
          data: { top: 0, left: 0, width: 0, height: 0, } as iCmsRangeItemData,
          restricts: [] as iCmsRangeItemData[],
        }
      });

      const handler = {
        mousedown: (e: MouseEvent) => {
          if (e.currentTarget !== e.target) {return;}

          document.body.addEventListener('mousemove', handler.mousemove);
          document.body.addEventListener('mouseup', handler.mouseup);
          const { left, top, height, width } = refEl.value!.getBoundingClientRect();
          const x = e.clientX - left;
          const y = e.clientY - top;
          state.drag = {
            dragging: false,
            start: { x, y },
            move: { x, y },
            data: { left: formatNumber(x / width), top: formatNumber(y / height), width: 0, height: 0, },
            restricts: props.list || [],
          };
        },
        mousemove: (e: MouseEvent) => {
          const { left, top } = refEl.value!.getBoundingClientRect();

          state.drag.move = { x: e.clientX - left, y: e.clientY - top };
          if (!state.drag.dragging) {
            if (Math.abs(state.drag.move.x - state.drag.start.x) < 5 && Math.abs(state.drag.move.y - state.drag.start.y) < 5) {
              return;
            } else {
              state.drag.dragging = true;
            }
          }
          const { height, width } = refEl.value!.getBoundingClientRect();

          const result = {
            prev: { ...state.drag.data },
            data: { ...state.drag.data },
          };

          const newWidth = (state.drag.move.x - state.drag.start.x) / width;
          if (newWidth > 0) {
            result.data.left = formatNumber(state.drag.start.x / width);
            result.data.width = formatNumber(newWidth);
          } else {
            result.data.left = formatNumber((state.drag.start.x / width) + newWidth);
            result.data.width = formatNumber(-newWidth);
          }

          const newHeight = (state.drag.move.y - state.drag.start.y) / height;
          if (newHeight > 0) {
            result.data.top = formatNumber(state.drag.start.y / height);
            result.data.height = formatNumber(newHeight);
          } else {
            result.data.top = formatNumber((state.drag.start.y / height) + newHeight);
            result.data.height = formatNumber(-newHeight);
          }

          processOverlap.mousemove({
            data: state.drag.data,
            state: {
              pos: {
                x: state.drag.move.x > state.drag.start.x ? 'right' : 'left',
                y: state.drag.move.y > state.drag.start.y ? 'bottom' : 'top',
              }
            },
            staticState: { restricts: state.drag.restricts },
            result
          });
        },
        mouseup: () => {
          state.drag.dragging = false;
          document.body.removeEventListener('mousemove', handler.mousemove);
          document.body.removeEventListener('mouseup', handler.mouseup);

          /*热区太小视为无效*/
          if (state.drag.data.width < 5 || state.drag.data.height < 5) {return;}
          /*热区重叠视为无效*/
          if (hasConflicts(state.drag.data, state.drag.restricts)) {return;}

          emit.onAdd(state.drag.data);
        },
      };

      const attrs = {
        [type === 'react' ? 'onMouseDown' : 'onMousedown']: handler.mousedown
      };

      provide('@@CMS_RANGE', { refEl, props });

      return () => (
        <div {...classes('cms-range-container-wrapper')} {...attrs} ref={refEl.onRef}>
          {slots.default()}
          {state.drag.dragging && <div {...classes('cms-range-item')} style={{
            top: `${state.drag.data.top}%`,
            left: `${state.drag.data.left}%`,
            height: `${state.drag.data.height}%`,
            width: `${state.drag.data.width}%`,
          }}/>}
        </div>
      );
    },
  });

  const CmsRangeItem = designComponent({
    props: {
      data: { type: Object, required: true },
    },
    slots: ['default'],
    setup({ props, slots }: { props: { data: iCmsRangeItemData }, slots: { default: () => RenderNode } }) {

      const { refEl: parentEl, props: parentProps } = inject('@@CMS_RANGE') as { refEl: { value: HTMLDivElement }, props: { list: iCmsRangeItemData[] } };
      const currentEl = createRef();
      const state = reactive({
        pos: {
          x: null as 'left' | 'right' | null,
          y: null as 'top' | 'bottom' | null,
        },
      });

      let staticState = {
        /*判断当前是否处于拖拽状态*/
        dragging: false,
        /*开始时鼠标位置位置*/
        start: {
          x: 0,
          y: 0,
        },
        /*移动时鼠标位置*/
        move: {
          x: 0,
          y: 0,
        },
        /*拖拽开始的时候props.data位置坐标数据备份*/
        data: {} as iCmsRangeItemData,
        /*用来判断重叠的数据*/
        restricts: [] as iCmsRangeItemData[],
      };

      const handler = {
        mousedown: (e: MouseEvent) => {

          document.body.addEventListener('mousemove', handler.mousemove);
          document.body.addEventListener('mouseup', handler.mouseup);
          staticState = {
            dragging: true,
            start: { x: e.clientX, y: e.clientY, },
            move: { x: e.clientX, y: e.clientY, },
            data: { ...props.data },
            restricts: parentProps.list.filter(i => i !== props.data),
          };
        },
        mousemove: (e: MouseEvent) => {
          staticState.move = { x: e.clientX, y: e.clientY };
          const { start, move, data } = staticState;
          const durX = move.x - start.x;
          const durY = move.y - start.y;
          const { width, height } = parentEl.value.getBoundingClientRect();
          const durW = formatNumber(durX / width);
          const durH = formatNumber(durY / height);

          const result = {
            prev: { ...props.data },
            data: { ...props.data },
          };

          if (!state.pos.x && !state.pos.y) {
            /*平移节点*/
            result.data.left = data.left + durW;
            result.data.top = data.top + durH;
          } else {
            /*缩放*/
            if (!!state.pos.x) {
              if (state.pos.x === "left") {
                /*拖拽左边界*/
                const newWidth = staticState.data.width - durW;
                if (newWidth > 0) {
                  result.data.left = staticState.data.left + staticState.data.width - newWidth;
                  result.data.width = newWidth;
                } else {
                  result.data.left = staticState.data.left + staticState.data.width;
                  result.data.width = -newWidth;
                }
              } else {
                /*拖拽右边界*/
                const newWidth = staticState.data.width + durW;
                if (newWidth > 0) {
                  result.data.left = staticState.data.left;
                  result.data.width = newWidth;
                } else {
                  result.data.left = staticState.data.left + newWidth;
                  result.data.width = -newWidth;
                }
              }
            }
            if (!!state.pos.y) {
              if (state.pos.y === "top") {
                /*拖拽上边界*/
                const newHeight = staticState.data.height - durH;
                if (newHeight > 0) {
                  result.data.top = staticState.data.top + staticState.data.height - newHeight;
                  result.data.height = newHeight;
                } else {
                  result.data.top = staticState.data.top + staticState.data.height;
                  result.data.height = -newHeight;
                }
              } else {
                /*拖拽下边界*/
                const newHeight = staticState.data.height + durH;
                if (newHeight > 0) {
                  result.data.top = staticState.data.top;
                  result.data.height = newHeight;
                } else {
                  result.data.top = staticState.data.top + newHeight;
                  result.data.height = -newHeight;
                }
              }
            }
          }

          processOverlap.mousemove({ data: props.data, state, staticState, result });
        },
        mouseup: () => {
          staticState.dragging = false;
          if (hasConflicts(props.data, staticState.restricts)) {
            console.log('mouseup conflict', { ...props.data });
            /*松开的时候检测如果有重叠，则重置位置为原来的位置*/
            Object.assign(props.data, staticState.data);
          }
          document.body.removeEventListener('mousemove', handler.mousemove);
          document.body.removeEventListener('mouseup', handler.mouseup);
        },
      };

      const attrs = {
        [type === 'react' ? 'onMouseDown' : 'onMousedown']: handler.mousedown,
        [type === 'react' ? 'onMouseMove' : 'onMousemove']: (e: MouseEvent) => {
          if (staticState.dragging) {return;}
          const { left, top, height, width } = currentEl.value!.getBoundingClientRect();
          state.pos.x = e.clientX < (left + 20) ? 'left' : e.clientX > (left + width - 20) ? 'right' : null;
          state.pos.y = e.clientY < (top + 20) ? 'top' : e.clientY > (top + height - 20) ? 'bottom' : null;
        },
      } as any;

      return () => (
        <div
          {...classes('cms-range-item')}
          {...attrs}
          ref={currentEl.onRef}
          data-x={state.pos.x}
          data-y={state.pos.y}
          style={{
            top: `${props.data.top}%`,
            left: `${props.data.left}%`,
            width: `${props.data.width}%`,
            height: `${props.data.height}%`,
          }}>
          {slots.default()}
        </div>
      );
    },
  });

  return {
    CmsRange,
    CmsRangeItem,
  };
}

const processOverlap = {
  mousemove: ({ data, result, staticState, state }: {
    result: { prev: iCmsRangeItemData, data: iCmsRangeItemData },
    staticState: { restricts: iCmsRangeItemData[] },
    data: iCmsRangeItemData
    state: {
      pos: {
        x: 'left' | 'right' | null,
        y: 'top' | 'bottom' | null,
      }
    }
  }) => {
    if (!hasConflicts(result.data, staticState.restricts)) {
      /*x轴，y轴都没有重叠*/
      Object.assign(data, result.data);
    } else {
      /*x轴是否有重叠*/
      const xHasConflict = hasConflicts({ ...result.data, top: result.prev.top, height: result.prev.height }, staticState.restricts);
      /*y轴是否有重叠*/
      const yHasConflict = hasConflicts({ ...result.data, left: result.prev.left, width: result.prev.width }, staticState.restricts);

      /*x有重叠则将x轴的状态撤回上一次的位置*/
      if (xHasConflict) {Object.assign(result.data, { left: result.prev.left, width: result.prev.width });}
      /*y有重叠则将y轴的状态撤回上一次的位置*/
      if (yHasConflict) {Object.assign(result.data, { top: result.prev.top, height: result.prev.height });}

      result.prev = { ...result.data };

      const duration = 0.01;
      const xDuration = 3;
      const yDuration = 3;

      /*贴合*/
      staticState.restricts.forEach(item => {
        if (result.data.left > item.left + item.width && result.data.left < item.left + item.width + xDuration) {
          /*左边贴合*/
          const oldDataLeft = result.data.left;
          result.data.left = item.left + item.width + duration;
          if (state.pos.x) {
            /*适应缩放宽度*/
            result.data.width = oldDataLeft + result.data.width - result.data.left;
          }
        } else if ((result.data.left + result.data.width) < item.left && (result.data.left + result.data.width) > item.left - xDuration) {
          /*右边贴合*/
          if (!state.pos.x) {
            result.data.left = item.left - duration - result.data.width;
          } else {
            result.data.width = item.left - duration - result.data.left;
          }
        } else if (result.data.left > item.left + item.width && result.data.left < item.left + item.width + yDuration) {
          /*上边贴合*/
          const oldDataTop = result.data.top;
          result.data.top = item.top + item.height + duration;
          if (state.pos.y) {
            /*适应缩放宽度*/
            result.data.height = oldDataTop + result.data.height - result.data.top;
          }
        } else if ((result.data.top + result.data.height) < item.top && (result.data.top + result.data.height) > item.top - yDuration) {
          /*右边贴合*/
          if (!state.pos.y) {
            result.data.top = item.top - duration - result.data.height;
          } else {
            result.data.height = item.top - duration - result.data.top;
          }
        }
      });
      if (hasConflicts(result.data, staticState.restricts)) {
        /*贴合可能导致重叠，如果贴合之后重叠的话，撤回贴个的变更*/
        Object.assign(result.data, result.prev);
      }

      Object.assign(data, result.data);
    }
  },
};
